"""
虚拟机管理模块
"""
import json
import requests
import time
from typing import Callable
from collections import defaultdict
from collections import OrderedDict
import traceback
from app.common.transaction_manager import create_transaction

from openstack import exceptions as ops_exceptions
from zunclient.common.apiclient import exceptions as zun_exceptions
from celery import group, chunks
from flask import current_app, request
from flask_restx import abort, reqparse
from openstack import exceptions

from tools.basic_tools import Tools
from app.api.v1.webhook.webhook_manager import WebHookManager
from app.api.v1.participant.base_view import ParticipantBase
from app.common.range_manager import RangeManager
from app.models import (
    ActiveRangeStatus,
    courseModel,
    ctfModel,
    rangeModel,
    switchModel,
    userModel,
    VmType,
    Image,
    StackTemplate,
    StackResource
)
from app.models.courseModel import Course
from tasks.tasks import change_active_vm, mark_failed_active_range, startVm
from tools import db, ops
from tools import redis_conn as redis_store
from utils.utils import (
    base_detail,
    get_task_status,
    get_tow_hours_stamp,
    init_openstack,
    wait_for_container_status,
)
from app.api.v1.organizer.rangeVm import Taas


class MyRange(ParticipantBase):
    def __init__(self, api=None, *args, **kwargs):
        super().__init__(api=api, *args, **kwargs)
        """
        根据客户定制配置虚拟化参数
        """
        self.conn = ops.conn
        # self.conn = init_openstack(self.user.project_id, current_app.config)

        """
        openstackAuthConfig = decrypt_pw(current_app.config['OPENSTACK']['auth'])
        openstackAuthConfig['project_id'] = self.user.project_id
        try:
            self.conn = openstack.connect(
                auth=openstackAuthConfig,
                auth_type=current_app.config['OPENSTACK']['auth_type'])
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="无法连接到云计算服务")
        """

    def wait_for_server_status(self, op_vm_uuid, status="ACTIVE"):
        # 再次获取虚拟机
        server = self.conn.compute.find_server(op_vm_uuid)
        # 等候状态
        self.conn.compute.wait_for_server(server, status)
        return

    # 检测课件ID，是否关联章节id，chapter_id:章节ID ；courseware_id：课件ID
    def checkMyRange_uuid(self, range_uuid, course_id, courseware_id):
        # 检测课件是否关联此靶场id
        course_info = courseModel.Course.query.filter_by(id=course_id).first()
        ok = False
        if (
            course_info.to_dict()["users"]
            and course_info.to_dict()["chapter_coursewares"]
        ):
            for user in course_info.to_dict()["users"]:
                if user["id"] == self.user.id:
                    ok = True
                    break
            if not ok:
                abort(400, message="参数错误")
            ok = False
            for c_c in course_info.to_dict()["chapter_coursewares"]:
                for courseware_id_info in c_c["child"]:
                    if (
                        str(courseware_id_info["courseware_id"]) == courseware_id
                        and str(courseware_id_info["range_uuid"]) == range_uuid
                    ):
                        ok = True
                        break

            if not ok:
                abort(400, message="参数错误")
            return ok

    def checkMyCtfRange(self, range_uuid):
        # 检测课件是否关联此靶场id
        ctfRange = ctfModel.Ctf.query.filter_by(range_uuid=range_uuid).first()
        if ctfRange:
            return ctfRange
        else:
            abort(400, message="参数错误")

    # 一个账号只能有一个活动靶场
    def getMyActiveRange(self):
        return rangeModel.ActiveRange.query.filter_by(user_id=self.user.id).first()

    # 获取外网入口IP
    def getMyActiveRangeIP(self, activeRange_range_uuid):
        return rangeModel.ActiveRangeVm.query.filter_by(
            activeRange_range_uuid=activeRange_range_uuid, is_external=1
        ).first()

    def getMyActiveRangeByUUID(self, range_uuid):
        return rangeModel.ActiveRange.query.filter_by(
            user_id=self.user.id, range_uuid=range_uuid
        ).first()

    def getMyRangeVmByUUID(self, range_uuid, new_vm_uuid=None):
        _filter = {"range_uuid": range_uuid, "user_id": self.user.id}
        if new_vm_uuid:
            _filter["new_vm_uuid"] = new_vm_uuid
            return rangeModel.ActiveRangeVm.query.filter_by(**_filter).first()
        else:
            return rangeModel.ActiveRangeVm.query.filter_by(**_filter).all()

    def getMyActiveRangeVmByUUID(self, activeRange_range_uuid):
        return rangeModel.ActiveRangeVm.query.filter_by(
            range_uuid=activeRange_range_uuid, user_id=self.user.id
        ).all()

    def getParentUser(self, project_id):
        from sqlalchemy import and_

        from app.models.roleModel import PresetRoleChoices, Role, UserRole

        return (
            userModel.User.query.join(
                UserRole, UserRole.user_id == userModel.User.id
            )
            .join(
                Role,
                and_(
                    Role.name == PresetRoleChoices.student.value,
                    UserRole.role_id == Role.id,
                ),
            )
            .filter(
                userModel.User.project_id == project_id,
            )
            .first()
        )

    def get_network_id(self, vm_info):
        return (
            switchModel.ActiveSwitchs.query.filter_by(range_uuid=vm_info.range_uuid)
            .first()
            .network_id
        )

    def start_firewall(self, vmInfo):
        """防火墙开机"""
        if not vmInfo.op_vm_uuid:
            op_vm_name = (
                    vmInfo.type + "-" + str(time.time()) + "-" + str(self.user.username)
            )
            # 检测当前我的靶场
            rangeInfo = self.getMyActiveRange()
            rangeInfo.network_id = self.get_network_id(vmInfo)

            nics = []
            for val in switchModel.ActiveSwitchs.query.filter_by(range_uuid=vmInfo.activeRange_range_uuid):
                # 获取交换机目标子网网关
                subnet = self.conn.get_subnet(val.subnet_id)
                subnet_gateway = subnet.gateway_ip

                # 获取该子网下所有端口，判断是否为该网关是否已经创建端口
                ports = self.conn.network.ports(
                    network_id=val.network_id,
                    subnet_id=val.subnet_id
                )
                # 检查网关ip是否已创建对应端口，并检查是否被占用
                for port in ports:
                    if port.fixed_ips[0]['ip_address'] == subnet_gateway:
                        if port.device_id:
                            raise ValueError(f"交换机{val.network_id} 网关端口被占用: {port}")
                        self.conn.update_port(port.id, port_security_enabled=False)
                        nics.append({"net-id": port.network_id, "port-id": port.id})
                        gateway_port = port
                        break
                # 未创建则进行创建
                else:
                    gateway_port = self.conn.create_port(
                        val.network_id,
                        fixed_ips=[
                            {"ip_address": subnet.gateway_ip, "subnet_id": val.subnet_id}
                        ],
                        port_security_enabled=False
                    )
                    nics.append({"net-id": gateway_port.network_id, "port-id": gateway_port.id})

                port_obj = switchModel.ActivePorts(
                    port_id=gateway_port.id,
                    range_uuid=vmInfo.activeRange_range_uuid,
                    ip=subnet.gateway_ip,
                    network_id=val.network_id,
                    subnet_id=val.subnet_id,
                    device_id=val.bind_firewall_uuid,
                    device_owner="firewall_gw",
                    user_id=self.user.id,
                )
                db.session.add(port_obj)
                db.session.commit()

            info = self.conn.create_server(
                op_vm_name,
                auto_ip=False,
                nics=nics,
                image=vmInfo.image,
                flavor=vmInfo.flavor,
                network=rangeInfo.network_id,
            )

            if info:
                rangeModel.ActiveRangeVm.query.filter_by(id=vmInfo.id).update(
                    {"op_vm_uuid": info.id}
                )
                db.session.commit()
        else:
            server = self.conn.compute.find_server(vmInfo.op_vm_uuid)
            if server.status != "ACTIVE":
                self.conn.compute.start_server(vmInfo.op_vm_uuid)

    def startVm(self, vmInfo):
        if not vmInfo.op_vm_uuid:
            op_vm_name = (
                vmInfo.type + "-" + str(time.time()) + "-" + str(self.user.username)
            )
            # 检测当前我的靶场
            rangeInfo = self.getMyActiveRange()
            rangeInfo.network_id = self.get_network_id(vmInfo)

            # 开始创建虚机
            nics = [{"net-id": rangeInfo.network_id}]
            if vmInfo.v4_fixed_ip:
                nics = [
                    {"net-id": rangeInfo.network_id, "v4-fixed-ip": vmInfo.v4_fixed_ip}
                ]

            if vmInfo.is_external:
                nics.append({"net-id": current_app.config["OPENSTACK"]["external_id"]})

            if vmInfo.type == VmType.container:
                image = Image.query.filter(Image.image_id == vmInfo.image).first()
                container_tags = image.tags
                environment = container_tags.get("environment", None)
                labels = container_tags.get("labels", None)

                container_nets = []
                for item in nics:
                    data = {"network": item["net-id"]}
                    if item.get("v4-fixed-ip"):
                        data.update({"v4-fixed-ip": item["v4-fixed-ip"]})
                    container_nets.append(data)
                info = ops.zun_operate.create_container(
                    image=image.image_full_name, name=op_vm_name,
                    cpu=container_tags.get("cpu", 0.2), memory=container_tags.get("memory", 500),
                    tty=True, interactive=True,
                    environment=environment or {
                        "foo": "bar"
                    },
                    labels=labels or {
                        "app": "hello"
                    },
                    image_driver="glance",
                    availability_zone="nova",
                    restart_policy={
                        "Name": "always",
                        "MaximumRetryCount": 0
                    },
                    nets=container_nets,
                )
            else:
                info = self.conn.create_server(
                    op_vm_name,
                    auto_ip=False,
                    nics=nics,
                    image=vmInfo.image,
                    flavor=vmInfo.flavor,
                    network=rangeInfo.network_id,
                )
                # self.wait_for_server_status(info.id)
            # 将虚机id写入api表
            if info:
                rangeModel.ActiveRangeVm.query.filter_by(id=vmInfo.id).update(
                    {"op_vm_uuid": info.id if vmInfo.type != VmType.container else info.uuid}
                )
                db.session.commit()

                if vmInfo.type == VmType.container:
                    try:
                        wait_for_container_status(
                            ops.zun_operate.zun_client, info.uuid, timeout=120, status="Created"
                        )
                    except:  # noqa
                        abort(400, message="开启中，请稍后")
                    else:
                        ops.zun_operate.start_container(info.uuid)
        else:
            if vmInfo.type != VmType.container:
                server = self.conn.compute.find_server(vmInfo.op_vm_uuid)
                if server.status != "ACTIVE":
                    self.conn.compute.start_server(vmInfo.op_vm_uuid)
                    # self.wait_for_server_status(vmInfo.op_vm_uuid)
            else:
                server = ops.zun_operate.get_container_details(vmInfo.op_vm_uuid)
                if server.status != "Running":
                    if server.status == "Creating":
                        try:
                            wait_for_container_status(
                                ops.zun_operate.zun_client, vmInfo.op_vm_uuid, status="Created"
                            )
                        except Exception:  # noqa
                            abort(400, message="开启中，请稍后")
                        else:
                            ops.zun_operate.start_container(server.uuid)
                    elif server.status in ["Created", "Stopped"]:
                        ops.zun_operate.start_container(server.uuid)
                    elif server.status == "Error":
                        abort(400, message="开启失败")

    @staticmethod
    def check_ctf_red_blue_is_me(user_id, ctf_id):
        from app.models.ctfModel import CtfRedBlueGroup, User

        group = CtfRedBlueGroup.query.filter(
            CtfRedBlueGroup.relate_user.any(User.id == user_id),
            CtfRedBlueGroup.ctf_red_blue_id == ctf_id,
        )
        if not db.session.query(group.exists()).scalar():
            abort(400, message="该比赛不是你参与的比赛")
        return group.first()


# 靶场列表分页
class MyRangeList(MyRange):
    def get(self):
        params = request.args
        page = params.get("page", 1)
        page_size = params.get("pageSize", 10)
        keyword = params.get("keyword")
        try:
            page = int(page)
            page_size = int(page_size)
        except Exception:
            page = 1
            page_size = 10

        if page_size >= 50:
            abort(400, message="每页数量不能超过50")

        try:
            filters = []
            if keyword:
                # 添加关键词的检索选项
                filters.append(rangeModel.Range.name.contains(keyword))

            parentInfo = self.getParentUser(self.user.project_id)
            if not parentInfo:
                abort(400, message="参数错误")
            paginate = (
                rangeModel.Range.query.filter(*filters)
                .filter_by(user_id=parentInfo.id, type=1)
                .order_by(rangeModel.Range.id.desc())
                .paginate(page, page_size, error_out=False)
            )

            info = {}
            info["current_page"] = page
            info["data"] = []
            if len(paginate.items) > 0:
                for content in paginate.items:
                    info["data"].append(
                        {
                            "name": content.name,
                            "range_uuid": content.rangeVmList[0].range_uuid,
                            "topology_img": content.topology_img,
                            "start": content.start,
                        }
                    )
            info = base_detail(info, paginate)
            return {"code": 200, "data": info}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="参数错误")


# 启动环境
class StartEnv(MyRange):
    
    def createNetwork(self, range_uuid, action_param):
        # 创建二层网络
        range_vni = rangeModel.RangeVni.query.first()
        network_id = Tools.get_uuid4_no_line()
        action_param["create_l2_vxlan_network"] = {
                "pool_uuid": range_vni.pool_uuid,
                "name": f"net-active-{range_uuid}",
                "zone_uuid": current_app.config["ZSTACK_CLOUD"]["zone_uuid"],
                "resource_uuid": network_id,
                "state": 1
            }
        return network_id

    # 床架交换机
    def crate_switch(self, range_uuid, switch_map, network_id, action_params):
        try:
            switch_con_active_switch = {}
            old_switch_id_new_switch = {}
            for switch, switch_info in switch_map.items():
                cidr = switch_info.cidr
                subnet_id = Tools.get_uuid4_no_line()
                obj = switchModel.ActiveSwitchs(
                    range_uuid=range_uuid,
                    subnet_id=subnet_id,
                    cidr=cidr,
                    network_id=network_id,
                    user_id = self.user.id
                )
                db.session.add(obj)
                db.session.flush()
                switch_con_active_switch[switch] = subnet_id
                old_switch_id_new_switch[str(switch_info.id)] = {
                    "id": obj.id,
                    "subnet_id": subnet_id
                }
                network_service = current_app.config["ZSTACK_CLOUD"]["network_service"]
                action_params[f"create_l3_network-{subnet_id}"] = {
                    "l2_network_uuid": network_id,
                    "name": switch_info.switch_name,
                    "resource_uuid": subnet_id
                }
                action_params[f"attach_network_service_to_l3Network-{subnet_id}"] = {
                        "l3_network_uuid": subnet_id,
                        "network_service": network_service,
                }
                action_params[f"create_l3_network_ip_ranges-{subnet_id}"] = {
                        "l3_network_uuid": subnet_id, 
                        "network_cidr": cidr,
                }
                action_params[f"add_dns_to_l3_network-{subnet_id}"] = {
                    "l3_network_id": subnet_id,
                    "dns": current_app.config["ZSTACK_CLOUD"]["dns"],
                }
            db.session.commit()
            return switch_con_active_switch, old_switch_id_new_switch
        except Exception:
            db.session.rollback()

    def create_router(self, range_uuid, router_list, old_switch_id_new_switch, action_params):
        route_conn_switch = {}
        for route in router_list:
            op_route = Tools.get_uuid4_no_line()
            old_binding_switchs = route.binding_switchs
            switch_ids = old_binding_switchs.split(",")
            binding_switchs = [
                str(old_switch_id_new_switch.get(switch_id).get("id")) for switch_id in switch_ids
            ]
            subnet_ids = [
                old_switch_id_new_switch.get(switch_id).get("subnet_id")
                for switch_id in switch_ids
            ]
            # 路由器与交换机对照
            route_conn_switch[op_route] = subnet_ids


            obj = switchModel.ActiveRoutes(op_route=op_route, range_uuid=range_uuid, user_id=self.user.id, 
                                     binding_switchs=",".join(binding_switchs))

            db.session.add(obj)
            db.session.flush()
            action_params[f"create_vpc_router-{op_route}"] = {
                "name": f"{op_route[:5]}-路由器",
                "router_offering_uuid": current_app.config["ZSTACK_CLOUD"]["router_uuid"],
                "resource_uuid": op_route,
            }
        db.session.commit()
        return route_conn_switch
        
    # 交换机与路由器连接
    def create_switch_subnet(self, range_uuid, route_conn_switch, network_id, action_params):
        for route, subnet_ids in route_conn_switch.items():
            for subnet_id in subnet_ids:
                # 入库
                obj = switchModel.ActivePorts(
                    port_id = Tools.get_uuid4_no_line(),
                    network_id=network_id,
                    range_uuid=range_uuid,
                    device_id=route,
                    device_owner="router_gw",
                    ip="",
                    subnet_id=subnet_id,
                    user_id=self.user.id
                )
                db.session.add(obj)
                db.session.flush()            
                # 交换机加服务
                action_params[f"attach_network_service_to_l3Network-{route}"] = {
                        "l3_network_uuid": subnet_id,
                        "network_service": {current_app.config["ZSTACK_CLOUD"]["network_route_eip_service"]: ["VRouterRoute"]},
                }
                action_params[f"attach_l3network_route-{subnet_id}"] = {
                    "route_uuid": route,
                    "l3_network_uuid": subnet_id   
                }
                action_params[f"update_active_vm_ip_database-{subnet_id}"] = {
                    "port_id": obj.port_id,
                    "device_type": "router",
                    "l3_network_uuid": subnet_id
                }
        db.session.commit()
        return
    
    # 创建虚拟机
    def create_vm(self, range_uuid, active_range_uuid, range_vm_list, switch_con_active_switch, network_id, action_params):
        # 入口
        nids = {}
        try:
            for vm in range_vm_list:
                l3_network_uuids = [switch_con_active_switch[vm.subnet_id]]
                device_type = "vm"
                # 入库
                vm_obj = rangeModel.ActiveRangeVm(
                    range_uuid=range_uuid,
                    activeRange_range_uuid=active_range_uuid,
                    type=vm.type,
                    name=vm.vm_name,
                    v4_fixed_ip=vm.v4_fixed_ip,
                    is_external=vm.is_external,
                    user_id=self.user.id,
                    image=vm.image,
                    flavor=vm.flavor,
                    subnet_id=switch_con_active_switch[vm.subnet_id],
                    op_vm_uuid=Tools.get_uuid4_no_line()
                )
                db.session.add(vm_obj)
                db.session.flush()
                # 创建port
                port_obj = switchModel.ActivePorts(
                    port_id = Tools.get_uuid4_no_line(),
                    network_id=network_id,
                    #range_uuid=active_range_uuid,
                    range_uuid=range_uuid,
                    device_id=vm_obj.new_vm_uuid,
                    device_owner="vm",
                    ip=vm_obj.v4_fixed_ip,
                    subnet_id=vm_obj.subnet_id,
                    user_id=self.user.id
                )
                db.session.add(port_obj)
                db.session.flush()
                if vm.type == "NIDS":
                    l3_network_uuids.insert(0, current_app.config["ZSTACK_CLOUD"]["mirror_network_uuid"])
                    nids[vm_obj.new_vm_uuid] = vm_obj.subnet_id
                    device_type = "NIDS"
                action_params[f"create_vm-{vm_obj.new_vm_uuid}"] = {
                    "name": vm_obj.name,
                    "instance_offering_uuid": vm_obj.flavor,
                    "image_uuid": vm_obj.image,
                    "dataDisk_offering_uuids": [], 
                    "resource_uuid": vm_obj.op_vm_uuid, 
                    "l3Network_uuids": l3_network_uuids,
                    "default_l3_network_uuid": l3_network_uuids[0],
                    "systemTags": [f"staticIp::{l3_network_uuids[-1]}::{vm_obj.v4_fixed_ip}"] if vm_obj.v4_fixed_ip else []
                }
                action_params[f"update_active_vm_ip_database-{vm_obj.new_vm_uuid}"] = {
                    "port_id": port_obj.port_id,
                    "device_type": device_type,
                    "op_vm_uuid": vm_obj.op_vm_uuid
                }   
                if vm.is_external:
                    vip_uuid, eip_uuid = Tools.get_uuid4_no_line(), Tools.get_uuid4_no_line()
                    public_network = current_app.config["ZSTACK_CLOUD"]["public_network"]
                    action_params[f"create_vip_action-{vm_obj.new_vm_uuid}"] = {"name": vm_obj.name, "l3_network_uuid": public_network, "resource_uuid": vip_uuid}
                    action_params[f"create_eip_action-{vm_obj.new_vm_uuid}"] = {"vip_uuid": vip_uuid, "resource_uuid": eip_uuid}
                    action_params[f"update_active_vm_external_ip_database-{vm_obj.new_vm_uuid}"] = {"vm_uuid": str(vm_obj.new_vm_uuid)}
                    action_params[f"attach_active_eip_to_vm-{vm_obj.new_vm_uuid}"] = {"eip_uuid": eip_uuid, "vm_uuid": str(vm_obj.new_vm_uuid)}
            db.session.commit()
        except Exception:
            db.session.rollback()
            print(traceback.print_exc())
        # nids
        for ni_key, ni_value in nids.items():
            port_obj = switchModel.ActivePorts.query.filter_by(subnet_id=ni_value, device_owner="vm").all()
            src_end_points_vm = [
                port.device_id for port in port_obj if port.device_id != ni_key
            ]
            #action_params[f"create_all_port_mirror_session-{ni_key}"] = {
            action_params[f"create_all_active_port_mirror_session-{ni_key}"] = {
                    "src_end_points_vm": src_end_points_vm, "nids_vm_uuid": str(ni_key)}

        action_params["update_all_active_operate_database"] = {
            "network_id": network_id,
            "range_uuid": active_range_uuid,
            "state": 1,
        }
        return
        
    
    def get_range_link_info(self, args, countdown):
        # sourcery skip: avoid-builtin-shadow

        # 1、判断是否存在活动靶场
        activeInfo = self.getMyActiveRange()
        if activeInfo:
            if activeInfo.range_uuid != args["range_uuid"]:
                return True, {
                    "code": 1001,
                    "data": "有正在学习的靶场环境，不能再次启动靶场",
                    "course_uuid": args["course_id"],
                }
            else:
                return True, {"code": 200, "data": "启动成功", "countdown": countdown, "range_uuid": activeInfo.activeRange_range_uuid}
                #return True, {"code": 200, "data": "启动成功", "range_uuid": activeInfo.activeRange_range_uuid}


        info = []
        # 2、假如是课件类的需 判断传入课程课件和靶场是否对应
        if args["course_id"] and args["courseware_id"]:
            info = self.checkMyRange_uuid(
                args["range_uuid"], args["course_id"], args["courseware_id"]
            )

        # 3、ctf比赛
        if args["ctf_id"]:
            myRange = self.checkMyCtfRange(args["range_uuid"])
            if myRange.id != args["ctf_id"]:
                abort(400, message="参数错误11")

            info = ctfModel.CtfLinkUser.query.filter_by(
                ctf_id=myRange.id, user_id=self.user.id
            ).first()

        return False, info

    def get_line(self, topo):
        """记录防火墙关联的交换机
        获取与防火墙相连的交换机网段
        """
        import json
        import itertools
        
        topo = json.loads(topo)

        switch_data = {}
        firewall_data = {}
        for val in topo["nodeList"]:
            if val["type"] == "switch":
                switch_data[val["id"]] = val["subnet_id"]
            if val["type"] == "firewall":
                firewall_data[val["id"]] = val["vm_uuid"]

        result = defaultdict(dict)
        for val in topo["lineList"]:
            d1, d2 = val.values()
            if d1 in switch_data and d2 in firewall_data:
                result[switch_data[d1]].update({"firewall_vm_uuid": firewall_data[d2]})
            elif d2 in switch_data and d1 in firewall_data:
                result[switch_data[d2]].update({"firewall_vm_uuid": firewall_data[d1]})
        return result

    def post(self):  # sourcery skip: extract-method
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", type=str, help="靶场UUID有误", required=True)
        parser.add_argument("course_id", type=str, help="课程ID有误")
        parser.add_argument("courseware_id", type=str, help="课件ID")
        parser.add_argument("ctf_id", type=int, help="ctf比赛ID")
        args = parser.parse_args()
        countdown = None
        if args["course_id"]:
            countdown = get_tow_hours_stamp(args["course_id"] + "_" + str(self.user.id))
        range_manager = RangeManager(range_uuid=args["range_uuid"], ignore_private=True)
        if range_manager.range_model.Range.is_draft:
            abort(400, message="场景未确认，请联系教师或管理员操作")

        flag, info = self.get_range_link_info(args, countdown)
        if flag:
            return info
        action_params = OrderedDict()
        if info:
            # 保存场景信息
            try:
                active_range = rangeModel.ActiveRange(range_uuid=args["range_uuid"], user_id=self.user.id, status="BUILDING")
                db.session.add(active_range)
                db.session.flush()
                # switch_firewall_line = self.get_line(range_manager.range_model.Range.topology_json)
                range_vm_list = range_manager.range_model.vmList
                # 获取场景中所有交换机
                switch_map = {
                    item.subnet_id: item
                    for item in switchModel.Switchs.query.filter_by(
                        range_uuid=args["range_uuid"]
                    ).all()
                }
                # 获取场景中所有路由
                router_list = switchModel.Routes.query.filter_by(
                    range_uuid=args["range_uuid"]
                ).all()
                # 创建基础网络
                network_id = self.createNetwork(active_range.range_uuid, action_params)
                # 交换机
                switch_con_active_switch, old_switch_id_new_switch = self.crate_switch(active_range.range_uuid, switch_map, network_id, action_params)
                # 路由器
                route_conn_switch = self.create_router(active_range.range_uuid, router_list, old_switch_id_new_switch, action_params)
                # 交换机与路由器关联
                self.create_switch_subnet(active_range.range_uuid, route_conn_switch, network_id, action_params)
                # 创建虚拟机
                self.create_vm(args["range_uuid"], active_range.activeRange_range_uuid, range_vm_list, switch_con_active_switch, network_id, action_params)
                
                create_transaction(action_params, active_range.range_uuid)
                
            except Exception:
                db.session.rollback()
                print(traceback.print_exc())

            #return {"code": 200, "data": "环境场景开启中", "countdown": countdown, "range_uuid": active_range.range_uuid}
            return {"code": 200, "data": "环境场景开启中", "countdown": countdown, "range_uuid": active_range.activeRange_range_uuid}
            #return {"code": 200, "data": "环境场景开启中", "range_uuid": active_range.activeRange_range_uuid}
        else:
            abort(400, message="无法定位该参与者即将创建环境的来源")



# 销毁当前用户活动靶场及虚拟机
class MyActiveRange(MyRange):
    def delete(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", type=str, help="靶场UUID有误", required=True)
        parser.add_argument("course_uuid")
        args = parser.parse_args()

        if not args["range_uuid"]:
            abort(400, message="参数错误")

        # 判断我是否有活动的靶场
        activeInfo = self.getMyActiveRange()
        if not activeInfo:
            abort(400, message="无活动靶场")
        elif activeInfo.status == "BUILDING":
            abort(400, message="有靶场正在创建，请稍后操作")

        rangeVmList = rangeModel.ActiveRangeVm.query.filter_by(
            user_id=self.user.id
        ).all()


        if rangeVmList:
            self._extracted_from_delete_stack(rangeVmList, args)
        db.session.delete(activeInfo)
        db.session.commit()

        if args["course_uuid"]:
            try:
                course_users = (
                    Course.query.filter_by(id=args["course_uuid"])
                    .first()
                    .course_users()
                )
                for user in course_users:
                    redis_store.delete(args["course_uuid"] + "_" + str(user["id"]))
            except Exception as e:
                current_app.logger.exception(e)

        return {"code": 200, "data": "销毁完成"}

    def _extracted_from_delete_stack(self, rangeVmList, args):
        # sourcery skip: avoid-builtin-shadow
        action_params = OrderedDict()
        for vm in rangeVmList:
            if vm.op_vm_uuid:
                action_params[f"del_vm_destroy-{vm.op_vm_uuid}"] = {"vm_uuid": vm.op_vm_uuid}
                action_params[f"del_vm_expunge-{vm.op_vm_uuid}"] = {"vm_uuid": vm.op_vm_uuid}
            # 删除虚拟ip
            if vm.is_external:
                action_params[f"del_vips_ip-{vm.op_vm_uuid}"] = {"vip_uuid": vm.eip_uuid}
            
        # 删除路由器
        route_list = switchModel.ActiveRoutes.query.filter_by(
            user_id=self.user.id
        ).all()
        for route in route_list:
            action_params[f"del_vm_destroy-{route.op_route}"] = {"vm_uuid": route.op_route}
        range = rangeModel.ActiveRange.query.filter_by(user_id = self.user.id).first()
        action_params["del_l2_network"] = {"network_id": range.network_id}
        create_transaction(action_params, args.range_uuid)


# 靶场下面虚机详细信息(课程)
class MyRangeVmInfos(MyRange):
    
    @property
    def load_cloud_object(self):
        return WebHookManager()
    
    def post(self):  # sourcery skip: avoid-builtin-shadow
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", type=str, help="靶场UUID有误", required=True)
        args = parser.parse_args()
        vm_info = []
        range = rangeModel.ActiveRange.query.filter_by(activeRange_range_uuid=args.range_uuid).first()
        #range = rangeModel.ActiveRange.query.filter_by(range_uuid=args.range_uuid).first()
        if  not  range:
            abort(400, message="未找到对应资源")
        if not range.network_id:
            return {"code": 200, "data": {"vm_list": vm_info}}
        # 获取学生场景的虚拟机
        range_vm_list = range.activeRangeVmList
        for vm in range_vm_list:
            result = self.load_cloud_object.get_vm_details(vm.op_vm_uuid)
            # 状态转换
            state = "ACTIVE" if result[0]["state"] == "Running" else "BUILDING"
            # 根据ip 和场景获取vm_name
            vm_info.append({    
                "id": result[0]["uuid"],
                "name": result[0]["name"],
                "status": state,
                "v4_fixed_ip": result[0]["vmNics"][0]["ip"],
                "type": result[0]["platform"] 
            })
        return {"code": 200, "data": {"vm_list": vm_info}}



# 操作虚拟机(课程)
class VmOperate(MyRange):
    
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("action", help="操作码错误", required=True)
        parser.add_argument("new_vm_uuid", help="虚机uuid不能为空", required=True)
        parser.add_argument("range_uuid", help=" 靶场参数有误", required=True)
        args = parser.parse_args()

        if not all([args["range_uuid"], args["new_vm_uuid"]]):
            abort(400, message="参数错误")

        try:
            # 3、 通过vm_uuid进行后续操作
            # 开机
            if args["action"] == "powerOn":
                return self._extracted_action_vm("start_vm", args, "开机正常")
            # 关机
            if args["action"] == "powerOff":
                return self._extracted_action_vm("stop_vm", args, "关机正常")
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="无法连接到虚拟机")


    def _extracted_action_vm(self, action, args, message):
        action_params = {action: {"vm_uuid": args.new_vm_uuid, "state": 1}}
        create_transaction(action_params, args.range_uuid)
        return {"code": 200, "info": message}


# 进入虚机
class VmEnter(MyRange):

    @property
    def load_cloud_object(self):
        return WebHookManager()     


    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场range_uuid有误", required=True)
        parser.add_argument("new_vm_uuid", help="虚拟机uuid有误", required=True)
        parser.add_argument("is_red_blue", default=0, type=int, required=False)
        args = parser.parse_args()

        range_manger = RangeManager(range_uuid=args["range_uuid"], ignore_private=True)

        if range_manger.range_model.Range.is_draft:
            abort(400, message="当前课程/比赛关联的场景需要创建人确认")

        # if args["is_red_blue"]:
        #     return self.get_red_blue_vnc(args, range_manger)

        try:
            # 获取noVnc地址
            if args["is_red_blue"]:
                vmInfo = range_manger.get_vm_from_this(vm_uuid=args.new_vm_uuid)
                vm_name = vmInfo.vm_name
                result = self.load_cloud_object.get_vm_console_address(vmInfo.op_vm_uuid)
            else:
                vmInfo = rangeModel.ActiveRangeVm.query.filter_by(op_vm_uuid=args.new_vm_uuid).first()
                vm_name = vmInfo.name
                result = self.load_cloud_object.get_vm_console_address(args.new_vm_uuid)
            localtion = result["location"]
            index = 0
            console_url = current_app.config["ZSTACK_CLOUD"]["console_url"]
            while index < 5:
                time.sleep(3)
                response = requests.get(url=localtion)
                if response.status_code == 200:
                    data = json.loads(response.text)["inventory"]
                    hostname = data["hostname"]
                    port = data["port"]
                    token = data["token"]
                    url = f"{console_url}/novnc?host={hostname}&port={port}&token={token}&title={vm_name}&vmCdRoms=true&language=zh-CN&platform={vmInfo.type}&ip={vmInfo.v4_fixed_ip}&guestToolsInstalled=false&lowVersion=false"
                    break
                index += 1
            info = {"url": url}          
        except Exception as e:
            current_app.logger.exception(e)
            abort(400, message="获取失败")
        else:
            return {"code": 200, "data": info}



# 比赛 启动 环境虚拟机
class StartEnvVm(MyRange):
    
    @property
    def load_cloud_object(self):
        return WebHookManager()
    
    @classmethod
    def aberrant_options(cls, *args, **kwargs):
        abort(400, message="操作选项异常")

    def powerOn(self, params, json_str, rangeInfo, vmList, *args, **kwargs):
        if rangeInfo.status in ActiveRangeStatus:
            return {"code": 200, "data": "环境已启动"}

        try:
            # 修改活动靶场状态
            rangeModel.ActiveRange.query.filter_by(id=rangeInfo.id).update(
                {"status": "BUILDING"}
            )
            db.session.commit()

        except Exception as e:
            db.session.rollback()
            current_app.logger.error("初次修改学生靶场活动状态失败", exc_info=e)
            abort(500, message="无法连接到虚拟机")
        else:
            try:
                # 批量为学生端靶场中虚拟机开机
                vm_task_group = group(
                    [
                        startVm.si(
                            self.user.username,
                            self.dumps_active_vm_info(vmInfo),
                            self.user.id,
                            params["range_uuid"],
                            json_str,
                        )
                        for vmInfo in vmList
                    ]
                )
                vm_task_group.link_error(
                    mark_failed_active_range.si(params["range_uuid"], self.user.id)
                )
                task_chords = vm_task_group | change_active_vm.si(
                    params["range_uuid"], self.user.id
                )
                task_chords.apply_async()
            except Exception as e:
                current_app.logger.error("学生靶场虚拟机批量开机任务失败注册失败", exc_info=e)

            return {"code": 200, "data": "环境启动成功"}

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument(
            "action", choices=("powerOn",), help="操作码异常: {error_msg}", required=True
        )
        parser.add_argument("ctf_id", help=" 比赛信息异常, 请重新输入", required=True)
        args = parser.parse_args()

        # # 获取活动靶场状态
        # rangeInfo = self.getMyActiveRangeByUUID(args["range_uuid"])
        # if rangeInfo and rangeInfo.status in ("ACTIVE", "BUILDING"):
        #     return {"code": 200, "data": "环境启动成功"}
        
        # 根据课程获取模板
        ctf_model = ctfModel.Ctf.query.filter_by(id=args.ctf_id).first()
        range_uuid = ctf_model.range_uuid
        
        
        # 查找是否学生已经存在重复的数据
        # resource_user_list = rangeModel.StackResource.query.filter_by(user_id=self.user.id).all()
        # if len(resource_user_list) > 1:
        #     abort(400, message="该学生已经存在比赛，请销毁其他资源")
            
        # for resource_user in resource_user_list:
        #     print(resource_user)
        #     if resource_user.range_uuid != range_uuid:
        #         abort(400, message="已经存在比赛，请销毁其他资源")
        #     else:
        #         return {"code": 200, "resource_id": resource_user.resource_stack_uuid}
        
        # stack_template = rangeModel.StackTemplate.query.filter_by(range_uuid=range_uuid).first()
        # stack_uuid = stack_template.stack_uuid
            
        # stack_name = ctf_model.name + self.user.account_name
        # stack_resource_uuid = Tools.get_uuid4_no_line()
        # self.load_cloud_object.create_resource_stack(stack_name, stack_uuid, stack_resource_uuid, range_uuid, self.user.id)
        
        # return {"code": 200, "resource_id": stack_resource_uuid}
        return {"code": 200}

    @classmethod
    def dumps_active_vm_info(cls, vm_info):
        return {
            "id": vm_info.id,
            "range_uuid": vm_info.range_uuid,
            "new_vm_uuid": vm_info.new_vm_uuid,
            "name": vm_info.name,
            "op_vm_uuid": vm_info.op_vm_uuid,
            "flavor": vm_info.flavor,
            "image": vm_info.image,
            "user_id": vm_info.user_id,
            "v4_fixed_ip": vm_info.v4_fixed_ip,
            "is_external": vm_info.is_external,
            "external_ip": vm_info.external_ip,
            "status": vm_info.status,
            "activeRange_range_uuid": vm_info.activeRange_range_uuid,
            "type": vm_info.type,
            "subnet_id": vm_info.subnet_id,
        }


# 获取当前靶场活动状态
class GetEnvVmStauts(MyRange):

    @property
    def load_cloud_object(self):
        return WebHookManager()

    def post(self):  # sourcery skip: avoid-builtin-shadow
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场参数有误", required=True)
        args = parser.parse_args()

        if not all([args["range_uuid"]]):
            abort(400, message="参数错误")

        addresses = ""
        # 获取活动靶场状态
        status = ""
        type = ""
        nids = False
        rangeInfo = self.getMyActiveRangeByUUID(args["range_uuid"])
        activate_vm_info = self.getMyRangeVmByUUID(args["range_uuid"])

        if rangeInfo:
            status = rangeInfo.status
            if rangeInfo.status == "ACTIVE":
                # 获取外网ip
                myRangeIP = self.getMyActiveRangeIP(rangeInfo.activeRange_range_uuid)
                for vm in activate_vm_info:
                    if vm.type == 'NIDS':
                        nids = True
                        nids_id = vm.new_vm_uuid

                    if vm.type not in ["NIDS", "HIDS"] and vm.is_external == 1:
                        addresses = myRangeIP.external_ip
                        type = myRangeIP.type

        vmInfo = [
            {
                "range_uuid": args["range_uuid"],
                "status": status,
                "addresses": addresses,
                "type": type,
            }
        ]
        if nids:

            port_ids = (
                db.session.query(switchModel.ActivePorts.port_id)
                    .filter(switchModel.ActivePorts.user_id == self.user.id, switchModel.ActivePorts.device_id != nids_id,
                            switchModel.ActivePorts.device_owner != 'vpn')
                    .all()
            )
            port_ids = [val[0] for val in port_ids]

            server_port = switchModel.ActivePorts.query.filter_by(device_id=nids_id).first()
            taas = Taas()
            tap_server_id = taas.create_tap_service(server_port.port_id, rangeInfo.activeRange_range_uuid)

            taas.create_flow(tap_server_id, port_ids, rangeInfo.activeRange_range_uuid)

        return {"code": 200, "data": {"range_info": vmInfo}}

    def get(self):
        """获取学生资源信息"""
        vm_info = rangeModel.ActiveRangeVm.query.filter_by(user_id=self.user.id, is_external=1).first()
        # 获取场景状态
        range = rangeModel.ActiveRange.query.filter_by(user_id=self.user.id).first()
        #if not range.status:
        if not range:
            state = "SHUTOFF"
        else:
            state = range.status
        
        return {"code": 200, "data": {"external_ip": vm_info.external_ip if vm_info else "", "status": state}}
    
# 获取该比赛下所有虚拟机(除了审计)rang_uuid,vm_uuid
class MyRedBlueRangeInfo(MyRange):
    def get(self, ctf_red_blue_id=None):

        if not ctf_red_blue_id:
            abort(400, message="ctf_id 必须提供")

        group = self.check_ctf_red_blue_is_me(self.user.id, ctf_red_blue_id)
        if group.group_type != ctfModel.GroupType.blue.value:
            abort(400, message="仅蓝方可进入靶场虚拟机内")

        range_uuid = group.ctf_red_blue.range_uuid
        if not range_uuid:
            abort(400, message="比赛未关联靶场")

        ctf = group.ctf_red_blue
        if ctf.status != ctf.StatusChoices.in_progress.value:
            abort(400, message="比赛不是进行中状态")

        range_manger = RangeManager(range_uuid=range_uuid, ignore_private=True)
        range_info = range_manger.range_model

        if range_info.Range.is_draft:
            abort(400, message="当前课程/比赛关联的场景需要创建人确认")

        # 单独提取入口web地址
        entry_range_vm = None
        info = []
        for item in range_info.vmList:
            if item.is_external == 1:
                entry_range_vm = item
            if item.type not in ("HIDS", "NIDS"):
                info.append(
                    {
                        "vm_name": item.vm_name,
                        "vm_uuid": item.vm_uuid,
                        "range_uuid": range_uuid,
                    }
                )

        # 进行中比赛，存在入口mis并开机
        if entry_range_vm and all(
            [
                entry_range_vm.op_vm_uuid,
                entry_range_vm.op_vm_name,
                ctf.status == ctf.StatusChoices.in_progress.value,
            ]
        ):
            if entry_range_vm.external_ip:
                external_ip = entry_range_vm.external_ip
            else:
                try:
                    mis_info = self.conn.get_server(entry_range_vm.op_vm_name)
                except Exception as e:
                    current_app.logger.exception("获取靶场入口mis信息失败", exc_info=e)
                    external_ip = ""
                else:
                    try:
                        addresses = list(mis_info.addresses.values())[1][0]["addr"]
                    except Exception as e:
                        current_app.logger.exception(
                            f"获取靶场入口地址信息失败：{mis_info.addresses}", exc_info=e
                        )
                        external_ip = ""
                    else:
                        entry_range_vm.external_ip = addresses
                        db.session.add(entry_range_vm)
                        db.session.commit()
                        external_ip = entry_range_vm.external_ip
        else:
            external_ip = ""

        return {
            "code": 200,
            "data": {
                "external_ip": external_ip,
                "describe": ctf.describe,
                "vm_info": info,
            },
        }
